package phoenics.manpads.common.mybatis.page;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;


import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 分页执行器
 *
 * @author Phoenics Chow
 * on 2018年12月09日
 */
public class PageExecutor implements Executor {
    private static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(PageExecutor.class);
    private Executor executor;
    public PageExecutor(Executor executor) {
        this.executor = executor;
    }

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        return executor.update(ms, parameter);
    }

    @Override
    public <E> List<E> query(MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
        RowBounds rb = new RowBounds(rowBounds.getOffset(), rowBounds.getLimit());
        List<E> rows = executor.query(mappedStatement, parameter, rowBounds, resultHandler,
                cacheKey, boundSql);
        return pageResolver(rows, mappedStatement, parameter, rb,(rowBounds instanceof RequestPage ));
    }
    @Override
    public <E> List<E> query(MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        return query(mappedStatement, parameter, rowBounds, resultHandler,
                executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql),
                boundSql);
    }

    private <E> List<E> pageResolver(List<E> rows, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds,boolean pageable) {
        if (pageable) {
            int count = getCount(mappedStatement, parameter);
            int offset = rowBounds.getOffset();
            int limit = rowBounds.getLimit();
            PageList<E> p=new PageList<>(count,offset,limit);
            p.addAll(rows);
            return p;
        }
        return rows;
    }
    private int getCount(MappedStatement mappedStatement, Object parameter) {
        BoundSql bsql = mappedStatement.getBoundSql(parameter);
        String sql = bsql.getSql();
        String countSql = "SELECT count(*) " +removeSelect(removeOrders(sql));
        DataSource dataSource = mappedStatement.getConfiguration().getEnvironment().getDataSource();
        try (Connection connection=dataSource.getConnection()) {
            PreparedStatement statement = connection.prepareStatement(countSql);
            setParameters(statement, mappedStatement, bsql, parameter);
            ResultSet rs = statement.executeQuery();
            if (rs.next())
                return rs.getInt(1);
        }catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }
    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
                               Object parameterObject) throws SQLException {

    }
    protected String removeOrders(String sql) {
        Pattern p = Pattern.compile("ORDER\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(sql);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }
    private static String removeSelect(String hql) {
        int beginPos = hql.toLowerCase().indexOf("from");
        if (beginPos < 0) {
            throw new IllegalArgumentException(" sql : " + hql + " must has a keyword 'from'");
        }
        return hql.substring(beginPos);
    }



    @Override
    public <E> Cursor<E> queryCursor(MappedStatement mappedStatement, Object o, RowBounds rowBounds) throws SQLException {
        return executor.queryCursor(mappedStatement,o,rowBounds);
    }

    @Override
    public List<BatchResult> flushStatements() throws SQLException {
        return executor.flushStatements();
    }

    @Override
    public void commit(boolean required) throws SQLException {
        executor.commit(required);
    }

    @Override
    public void rollback(boolean required) throws SQLException {
        executor.rollback(required);
    }

    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        return executor.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    }

    @Override
    public boolean isCached(MappedStatement ms, CacheKey key) {
        return executor.isCached(ms, key);
    }

    @Override
    public void clearLocalCache() {
        executor.clearLocalCache();
    }

    @Override
    public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
        executor.deferLoad(ms, resultObject, property, key, targetType);
    }

    @Override
    public Transaction getTransaction() {
        return executor.getTransaction();
    }

    @Override
    public void close(boolean forceRollback) {
        executor.close(forceRollback);
    }

    @Override
    public boolean isClosed() {
        return executor.isClosed();
    }

    @Override
    public void setExecutorWrapper(Executor executor) {
        executor.setExecutorWrapper(executor);
    }
}
